using System;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using DirectShowLib;
using JAP.NET.Properties;

namespace JAP.NET
{
    internal enum PlayerState
    {
        Running,
        Stopped,
        Paused,
        Init
    } ;

    internal enum FileType
    {
        VideoFile,
        AudioFile
    } ;

    public partial class fmMain : Form
    {
        // constants
        private const int VolumeMin = -8000;
        private const int WMGraphNotify = 0x0400 + 13;

        // interfaces
        private IBasicAudio basicAudio;
        private IBasicVideo basicVideo;
        private double currentPos;
        private PlayerState currentState = PlayerState.Stopped;
        private int currentVolume = -4000;

        // other vars
        private string filename = string.Empty;
        private IVideoFrameStep frameStep;
        private IGraphBuilder graphBuilder;
        private IntPtr hDrain = IntPtr.Zero;
        private bool isAudioOnly;
        private IMediaControl mediaControl;
        private IMediaEventEx mediaEventEx;
        private IMediaPosition mediaPosition;
        private IMediaSeeking mediaSeeking;
        private int storedVolume;
        private IVideoWindow videoWindow;


        public fmMain()
        {
            InitializeComponent();
        }

        private void OpenClip()
        {
            if (currentState == PlayerState.Running)
            {
                if (
                    MessageBox.Show("Stop playing the file?", Application.ProductName, MessageBoxButtons.OKCancel,
                                    MessageBoxIcon.Question) == DialogResult.Cancel) return;
            }
            try
            {
                filename = String.Empty;
                UpdateMainTitle();
                filename = GetClipFileName();
                if (filename == string.Empty)
                    return;
                // Reset status variables
                currentState = PlayerState.Stopped;
                currentVolume = tbVolume.Value;
                currentPos = 0;
    
                // Start playing the media file
                PlayMovieInWindow(filename);
            }
            catch
            {
                CloseClip();
            }
        }

        private void PlayMovieInWindow(string fileName)
        {
            if (string.IsNullOrEmpty(fileName))
                return;
            currentVolume = tbVolume.Value;
            graphBuilder = (IGraphBuilder) new FilterGraph();

            // Have the graph builder construct its the appropriate graph automatically
            int hr = graphBuilder.RenderFile(fileName, null);
            DsError.ThrowExceptionForHR(hr);

            // QueryInterface for DirectShow interfaces
            mediaControl = (IMediaControl) graphBuilder;
            mediaEventEx = (IMediaEventEx) graphBuilder;
            mediaSeeking = (IMediaSeeking) graphBuilder;
            mediaPosition = (IMediaPosition) graphBuilder;

            // Query for video interfaces, which may not be relevant for audio files
            videoWindow = graphBuilder as IVideoWindow;
            basicVideo = graphBuilder as IBasicVideo;

            // Query for audio interfaces, which may not be relevant for video-only files
            basicAudio = graphBuilder as IBasicAudio;
            if (basicAudio == null)
            {
                tbVolume.Enabled = false;
            }
            else
            {
                basicAudio.put_Volume(tbVolume.Value);
            }

            // Is this an audio-only file (no video component)?
            CheckVisibility();

            // Have the graph signal event via window callbacks for performance
            hr = mediaEventEx.SetNotifyWindow(Handle, WMGraphNotify, IntPtr.Zero);
            DsError.ThrowExceptionForHR(hr);

            if (!isAudioOnly)
            {
                // Setup the video window
                if (videoWindow != null) hr = videoWindow.put_Owner(pVideoWin.Handle);
                DsError.ThrowExceptionForHR(hr);

                if (videoWindow != null)
                    hr =
                        videoWindow.put_WindowStyle(WindowStyle.Child | WindowStyle.ClipSiblings |
                                                    WindowStyle.ClipChildren);
                DsError.ThrowExceptionForHR(hr);

                hr = InitVideoWindow(1, 1);
                DsError.ThrowExceptionForHR(hr);

                GetFrameStepInterface();
            }
            else
            {
                // Initialize the default player size and enable playback menu items
                hr = InitPlayerWindow();
                DsError.ThrowExceptionForHR(hr);
            }

            // Complete window initialization
            UpdateMainTitle();

            Focus();

            SeekerSetup();

            // Run the graph to play the media file
            tTicker.Enabled = true;
            tsPlay.Enabled = false;
            tsPause.Enabled = true;
            mediaControl.Run();
            DsError.ThrowExceptionForHR(hr);

            currentState = PlayerState.Running;
            pVideoWin.Dock = DockStyle.None;
            pVideoWin.Dock = DockStyle.Fill;


            if (!isAudioOnly)
            {
                MoveVideoWindow();
            }
        }

        private void CloseClip()
        {
            tTicker.Enabled = false;
            // Stop media playback
            if (mediaControl != null)
                mediaControl.Stop();

            // Clear global flags
            currentState = PlayerState.Stopped;
            isAudioOnly = true;

            // Free DirectShow interfaces
            CloseInterfaces();

            // Clear file name to allow selection of new file with open dialog
            filename = string.Empty;

            // No current media state
            currentState = PlayerState.Init;
            tsPlay.Enabled = true;
            tsPause.Enabled = false;
            tsDecRate.Enabled = false;
            tsIncRate.Enabled = false;

            UpdateMainTitle();
            //InitPlayerWindow();
        }

        private int InitVideoWindow(int nMultiplier, int nDivider)
        {
            int lHeight, lWidth;

            if (basicVideo == null)
                return 0;

            // Read the default video size
            int hr = basicVideo.GetVideoSize(out lWidth, out lHeight);
            if (hr == DsResults.E_NoInterface)
                return 0;

            // Account for requests of normal, half, or double size
            lWidth = lWidth*nMultiplier/nDivider;
            lHeight = lHeight*nMultiplier/nDivider;


            pVideoWin.ClientSize = new Size(lWidth, lHeight);
            Application.DoEvents();

            hr = videoWindow.SetWindowPosition(0, 0, lWidth, lHeight);

            return hr;
        }

        private void MoveVideoWindow()
        {
            // Track the movement of the container window and resize as needed
            if (videoWindow == null) return;
            int hr = videoWindow.SetWindowPosition(
                pVideoWin.ClientRectangle.Left,
                pVideoWin.ClientRectangle.Top,
                pVideoWin.ClientRectangle.Width,
                pVideoWin.ClientRectangle.Height
                );
            DsError.ThrowExceptionForHR(hr);
        }

        private void CheckVisibility()
        {
            OABool lVisible;

            if ((videoWindow == null) || (basicVideo == null))
            {
                // Audio-only files have no video interfaces.  This might also
                // be a file whose video component uses an unknown video codec.
                isAudioOnly = true;
                return;
            }
            // Clear the global flag
            isAudioOnly = false;

            int hr = videoWindow.get_Visible(out lVisible);
            if (hr < 0)
            {
                // If this is an audio-only clip, get_Visible() won't work.
                //
                // Also, if this video is encoded with an unsupported codec,
                // we won't see any video, although the audio will work if it is
                // of a supported format.
                if (hr == unchecked((int) 0x80004002)) //E_NOINTERFACE
                {
                    isAudioOnly = true;
                }
                else
                    DsError.ThrowExceptionForHR(hr);
            }
            tsFullScreen.Enabled = !isAudioOnly;
        }

        //
        // Some video renderers support stepping media frame by frame with the
        // IVideoFrameStep interface.  See the interface documentation for more
        // details on frame stepping.
        //
        private void GetFrameStepInterface()
        {
            // Get the frame step interface, if supported
            var frameStepTest = (IVideoFrameStep) graphBuilder;

            // Check if this decoder can step
            int hr = frameStepTest.CanStep(0, null);
            if (hr == 0)
            {
                frameStep = frameStepTest;
                tsDecRate.Enabled = true;
                tsIncRate.Enabled = true;
                return;
            }
            frameStep = null;
            tsDecRate.Enabled = false;
            tsIncRate.Enabled = false;
            return;
        }

        private void CloseInterfaces()
        {
            try
            {
                lock (this)
                {
                    // Relinquish ownership (IMPORTANT!) after hiding video window
                    int hr;
                    if (!isAudioOnly)
                    {
                        hr = videoWindow.put_Visible(OABool.False);
                        DsError.ThrowExceptionForHR(hr);
                        hr = videoWindow.put_Owner(IntPtr.Zero);
                        DsError.ThrowExceptionForHR(hr);
                    }

                    if (mediaEventEx != null)
                    {
                        hr = mediaEventEx.SetNotifyWindow(IntPtr.Zero, 0, IntPtr.Zero);
                        DsError.ThrowExceptionForHR(hr);
                    }

                    // Release and zero DirectShow interfaces
                    if (mediaEventEx != null)
                        mediaEventEx = null;
                    if (mediaSeeking != null)
                        mediaSeeking = null;
                    if (mediaPosition != null)
                        mediaPosition = null;
                    if (mediaControl != null)
                        mediaControl = null;
                    if (basicAudio != null)
                        basicAudio = null;
                    if (basicVideo != null)
                        basicVideo = null;
                    if (videoWindow != null)
                        videoWindow = null;
                    if (frameStep != null)
                        frameStep = null;
                    if (graphBuilder != null)
                        Marshal.ReleaseComObject(graphBuilder);
                    graphBuilder = null;

                    GC.Collect();
                }
            }
            catch (Exception ex)
            {
                Trace.WriteLine(ex.ToString());
            }
        }


        private void PauseClip()
        {
            if (mediaControl == null)
                return;

            // Toggle play/pause behavior
            if ((currentState == PlayerState.Paused) || (currentState == PlayerState.Stopped))
            {
                if (mediaControl.Run() >= 0)
                    currentState = PlayerState.Running;
                tsPlay.Enabled = false;
                tsPause.Enabled = true;
            }
            else
            {
                if (mediaControl.Pause() >= 0)
                    currentState = PlayerState.Paused;
                tsPlay.Enabled = true;
                tsPause.Enabled = false;
            }

            UpdateMainTitle();
        }

        private void StopClip()
        {
            var pos = new DsLong(0);

            if ((mediaControl == null) || (mediaSeeking == null))
                return;

            // Stop and reset postion to beginning
            if ((currentState == PlayerState.Paused) || (currentState == PlayerState.Running))
            {
                mediaControl.Stop();
                currentState = PlayerState.Stopped;

                // Seek to the beginning
                mediaSeeking.SetPositions(pos, AMSeekingSeekingFlags.AbsolutePositioning, null,
                                          AMSeekingSeekingFlags.NoPositioning);

                // Display the first frame to indicate the reset condition
                mediaControl.Pause();
            }
            UpdateMainTitle();
            tsPlay.Enabled = true;
            tsPause.Enabled = false;
        }

        private void ToggleMute()
        {
            if ((graphBuilder == null) || (basicAudio == null))
                return;

            // Read current volume
            int hr = basicAudio.get_Volume(out currentVolume);
            if (hr == -1) //E_NOTIMPL
            {
                // Fail quietly if this is a video-only media file
                return;
            }
            if (hr < 0) return;
            if (currentVolume != VolumeMin)
            {
                storedVolume = currentVolume;
                currentVolume = VolumeMin;
                tbVolume.Value = currentVolume;
                tsMute.Image = Resources.volume;
                tsMute.ToolTipText = "Toggle Mute Off";
            }
            else
            {
                currentVolume = storedVolume;
                storedVolume = 0;
                tbVolume.Value = currentVolume;
                tsMute.Image = Resources.mute;
                tsMute.ToolTipText = "Toggle Mute On";
            }

            // Set new volume
            if (basicAudio != null) basicAudio.put_Volume(currentVolume);

            UpdateMainTitle();
            return;


            // Switch volume levels
        }

        private void ToggleFullScreen()
        {
            OABool lMode;

            // Don't bother with full-screen for audio-only files
            if ((isAudioOnly) || (videoWindow == null))
                return;

            // Read current state
            int hr = videoWindow.get_FullScreenMode(out lMode);
            DsError.ThrowExceptionForHR(hr);

            if (lMode == OABool.False)
            {
                // Save current message drain
                hr = videoWindow.get_MessageDrain(out hDrain);
                DsError.ThrowExceptionForHR(hr);

                // Set message drain to application main window
                hr = videoWindow.put_MessageDrain(Handle);
                DsError.ThrowExceptionForHR(hr);

                // Switch to full-screen mode
                lMode = OABool.True;
                hr = videoWindow.put_FullScreenMode(lMode);
                DsError.ThrowExceptionForHR(hr);
            }
            else
            {
                // Switch back to windowed mode
                lMode = OABool.False;
                hr = videoWindow.put_FullScreenMode(lMode);
                DsError.ThrowExceptionForHR(hr);

                // Undo change of message drain
                hr = videoWindow.put_MessageDrain(hDrain);
                DsError.ThrowExceptionForHR(hr);

                // Reset video window
                hr = videoWindow.SetWindowForeground(OABool.True);
                DsError.ThrowExceptionForHR(hr);

                // Reclaim keyboard focus for player application
                //this.Focus();
            }

            return;
        }

        private int StepOneFrame()
        {
            var hr = 0;

            // If the Frame Stepping interface exists, use it to step one frame
            if (frameStep != null)
            {
                // The graph must be paused for frame stepping to work
                if (currentState != PlayerState.Paused)
                    PauseClip();

                // Step the requested number of frames, if supported
                hr = frameStep.Step(1, null);
            }

            return hr;
        }

        private int StepFrames(int nFramesToStep)
        {
            var hr = 0;

            // If the Frame Stepping interface exists, use it to step frames
            if (frameStep != null)
            {
                // The renderer may not support frame stepping for more than one
                // frame at a time, so check for support.  S_OK indicates that the
                // renderer can step nFramesToStep successfully.
                hr = frameStep.CanStep(nFramesToStep, null);
                if (hr == 0)
                {
                    // The graph must be paused for frame stepping to work
                    if (currentState != PlayerState.Paused)
                        PauseClip();

                    // Step the requested number of frames, if supported
                    hr = frameStep.Step(nFramesToStep, null);
                }
            }

            return hr;
        }

        private void ModifyRate(double dRateAdjust)
        {
            // If the IMediaPosition interface exists, use it to set rate
            if ((mediaPosition != null) && (dRateAdjust != 0.0))
            {
                double dRate;
                var hr = mediaPosition.get_Rate(out dRate);
                if (hr == 0)
                {
                    // Add current rate to adjustment value
                    var dNewRate = dRate + dRateAdjust;
                    hr = mediaPosition.put_Rate(dNewRate);

                    // Save global rate
                    if (hr == 0)
                    {
                        UpdateMainTitle();
                    }
                }
            }

            return;
        }

        private void SetRate(double rate)
        {
            // If the IMediaPosition interface exists, use it to set rate
            if (mediaPosition != null)
            {
                var hr = mediaPosition.put_Rate(rate);
                if (hr >= 0)
                {
                    UpdateMainTitle();
                }
            }

            return;
        }

        private void ModifyVolume(int newVolume)
        {
            if ((newVolume < -8000) || (newVolume > 0)) newVolume = 0;
            if (basicAudio == null) return;
            currentVolume = newVolume;
            var hr = basicAudio.put_Volume(currentVolume);
            DsError.ThrowExceptionForHR(hr);
            return;
        }

        private void HandleGraphEvent()
        {
            EventCode evCode;
            IntPtr evParam1, evParam2;

            // Make sure that we don't access the media event interface
            // after it has already been released.
            if (mediaEventEx == null)
                return;

            // Process all queued events
            while (mediaEventEx.GetEvent(out evCode, out evParam1, out evParam2, 0) == 0)
            {
                // Free memory associated with callback, since we're not using it
                mediaEventEx.FreeEventParams(evCode, evParam1, evParam2);

                // If this is the end of the clip, reset to beginning
                if (evCode != EventCode.Complete) continue;
                var pos = new DsLong(0);
                // Reset to first frame of movie
                var hr = mediaSeeking.SetPositions(pos, AMSeekingSeekingFlags.AbsolutePositioning,
                                                   null, AMSeekingSeekingFlags.NoPositioning);
                if (hr >= 0) continue;
                // Some custom filters (like the Windows CE MIDI filter)
                // may not implement seeking interfaces (IMediaSeeking)
                // to allow seeking to the start.  In that case, just stop
                // and restart for the same effect.  This should not be
                // necessary in most cases.
                mediaControl.Stop();
                mediaControl.Run();
            }
        }

        protected override void WndProc(ref Message m)
        {
            switch (m.Msg)
            {
                case WMGraphNotify:
                    {
                        HandleGraphEvent();
                        break;
                    }
            }

            // Pass this message to the video window for notification of system changes
            if (videoWindow != null)
                videoWindow.NotifyOwnerMessage(m.HWnd, m.Msg, m.WParam, m.LParam);

            base.WndProc(ref m);
        }

        private string GetClipFileName()
        {
            if (filename != String.Empty) return filename;
            var Dlg = new OpenFileDialog
                          {
                              InitialDirectory = Environment.CurrentDirectory,
                              Filter = "Video Files|*.avi|MP3|*.mp3"
                          };
            return Dlg.ShowDialog() == DialogResult.OK ? Dlg.FileName : String.Empty;
        }

        private static int InitPlayerWindow()
        {
            // Reset to a default size for audio and after closing a clip
            // do something like fractal building or picture loading...

            return 0;
        }

        private void UpdateMainTitle()
        {
            // If no file is loaded, just show the application title
            if (filename == string.Empty)
                Text = Application.ProductName;
            else
            {
                var media = (isAudioOnly) ? "Audio" : "Video";
                var muted = (currentVolume == VolumeMin) ? "Mute" : "";
                var paused = (currentState == PlayerState.Paused) ? "Paused" : "";

                Text = String.Format("{4} - {0} [{1}] {2}{3}", Path.GetFileName(filename), media, muted, paused,
                                     Application.ProductName);
            }
        }

        private void exitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            CloseClip();
            Close();
        }

        private void openFileToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (currentState != PlayerState.Init)
            {
                CloseClip();
            }
            OpenClip();
        }

        private void fmMain_FormClosing(object sender, FormClosingEventArgs e)
        {
            StopClip();
            CloseInterfaces();
        }

        private void fmMain_Move(object sender, EventArgs e)
        {
            if (!isAudioOnly)
            {
                MoveVideoWindow();
            }
        }

        private void fmMain_Resize(object sender, EventArgs e)
        {
            if (!isAudioOnly)
            {
                MoveVideoWindow();
            }
        }

        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        private static void Main(string[] args)
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            using (var fmain = new fmMain())
            {
                if (args.Length > 0)
                {
                    fmain.filename = args[0];
                }
                Application.Run(fmain);
            }
        }

        private void fmMain_Shown(object sender, EventArgs e)
        {
            tbVolume.Value = currentVolume;
        }

        private void SeekerSetup()
        {
            double FullLength;
            if (mediaPosition == null) return;
            var hr = mediaPosition.get_Duration(out FullLength);
            DsError.ThrowExceptionForHR(hr);
            currentPos = 0;
            tbSeeker.Maximum = (int) Math.Max(FullLength, 0);
            tbSeeker.TickFrequency = (int) Math.Max((double) tbSeeker.Maximum/10, 0);
            tbSeeker.Minimum = 0;
            tbSeeker.Value = 0;
            Application.DoEvents();
        }

        private void SetPosition(int newPosition)
        {
            if (mediaPosition == null) return;
            currentPos = newPosition;
            var hr = mediaPosition.put_CurrentPosition(currentPos);
            DsError.ThrowExceptionForHR(hr);
        }

        private void UpdateSeeker()
        {
            tbSeeker.Value = (int) currentPos;
        }

        private void tTicker_Tick(object sender, EventArgs e)
        {
            if (mediaPosition == null) return;
            double pos;
            int hr = mediaPosition.get_CurrentPosition(out pos);
            DsError.ThrowExceptionForHR(hr);
            currentPos = pos;
            UpdateSeeker();
        }


        private void tbSeeker_Scroll(object sender, EventArgs e)
        {
            SetPosition(tbSeeker.Value);
        }

        private void btOpen_Click(object sender, EventArgs e)
        {
            OpenClip();
        }

        private void btStop_Click(object sender, EventArgs e)
        {
            StopClip();
        }

        private void btPlay_Click(object sender, EventArgs e)
        {
            PauseClip();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            ToggleFullScreen();
        }

        private void button2_Click(object sender, EventArgs e)
        {
            ToggleMute();
        }

        private void tbVolume_Scroll(object sender, EventArgs e)
        {
            ModifyVolume(tbVolume.Value);
        }

        private void btIncRate_Click(object sender, EventArgs e)
        {
            ModifyRate(+0.25);
        }

        private void btDecRate_Click(object sender, EventArgs e)
        {
            ModifyRate(-0.25);
        }

        private void resetRateToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (sender == resetRateToolStripMenuItem) SetRate(1.0);
            if (sender == doubleRateToolStripMenuItem) SetRate(2.0);
            if (sender == halfRateToolStripMenuItem) SetRate(0.5);
        }

        private void fmMain_Load(object sender, EventArgs e)
        {
            if (!string.IsNullOrEmpty(filename))
            {
                PlayMovieInWindow(filename);
            }
        }

        private void fmMain_KeyDown(object sender, KeyEventArgs e)
        {
            e.Handled = true;
            switch (e.KeyCode)
            {
                case Keys.Enter:
                    {
                        ToggleFullScreen();
                        break;
                    }
                case Keys.Space:
                    {
                        PauseClip();
                        break;
                    }
                case Keys.M:
                    {
                        ToggleMute();
                        break;
                    }
            }
        }

        private void tsFilters_Click(object sender, EventArgs e)
        {
            var filters = new fmFilters {filterBuilder = graphBuilder};
            filters.ShowDialog(this);
        }
    }
}